﻿using System;
using System.Collections.Generic;
using System.IO;

namespace DivaScriptConv.DivaData
{
    public unsafe class DivaStream : IDisposable
    {
        private Stream _Stream;
        // private byte _CurrentBitReader = 0;
        // private byte _CurrentValReader = 0;
        // private byte _CurrentBitWriter = 0;
        // private byte _CurrentValWriter = 0;
        private byte[] _buf = new byte[16];

        public bool IsBE { get; set; }
        public bool IsX { get; set; }

        private DivaData.Format _format = DivaData.Format.NULL;
        public DivaData.Format Format
        {
            get { return _format; }
            set
            {
                _format = value;
                IsBE = _format == DivaData.Format.F2BE;
                IsX = _format == DivaData.Format.X || _format == DivaData.Format.XHD;
            }
        }

        public int Length
        {
            get { return (int)_Stream.Length; }
            set { SetLength(value); }
        }
        public long LongLength
        {
            get { return _Stream.Length; }
            set { SetLength(value); }
        }

        public int Position
        {
            get { return (int)_Stream.Position; }
            set { Seek(value, 0); }
        }
        public long LongPosition
        {
            get { return _Stream.Position; }
            set { Seek(value, 0); }
        }

        public void Close() { Dispose(); }
        public void Flush() { _Stream.Flush(); }
        public long Seek(long offset, SeekOrigin origin) { return _Stream.Seek(offset, origin); }
        public void SetLength(long length) { _Stream.SetLength(length); }

        #region IDisposable Support

        private bool disposedValue = false; // To detect redundant calls

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    if (_Stream != Stream.Null) _Stream.Flush(); _Stream.Dispose();
                }
                disposedValue = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }

        #endregion

        public DivaStream(Stream output = null, bool isBE = false)
        {
            if (output == null) output = Stream.Null;
            // _CurrentBitReader = 8;
            // _CurrentValReader = _CurrentBitWriter = _CurrentValWriter = 0;
            _Stream = output;
            Format = DivaData.Format.NULL;
            _buf = new byte[16];
            IsBE = isBE;
        }

        public static DivaStream OpenReader(byte[] Data) { return new DivaStream(new MemoryStream(Data)); }
        public static DivaStream OpenReader(string file) { return new DivaStream(new FileStream(file, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)); }

        public Header ReadHeader(bool Seek)
        {
            if (Seek)
                if (Position >= 4)
                    this.Seek(-4, SeekOrigin.Current);
                else
                    this.Seek(0, 0);
            return ReadHeader();
        }

        public Header ReadHeader()
        {
            long Position = LongPosition;
            Header Header = new Header
            {
                Format = DivaData.Format.F2LE,
                Signature = ReadInt32(),
                DataSize = ReadInt32(),
                Lenght = ReadInt32()
            };
            if (ReadUInt32() == 0x18000000)
            { Header.IsBE = true; Header.Format = DivaData.Format.F2BE; }
            Header.ID = ReadInt32();
            Header.SectionSize = ReadInt32();
            IsBE = Header.IsBE;
            Format = Header.Format;
            LongPosition = Position + Header.Lenght;
            Header.Signature = ReadInt32Endian();
            return Header;
        }

        public long IntFromArray(sbyte L) => IntFromArrayEndian(L, false);
        public long IntFromArrayEndian(sbyte L, bool IsBE)
        {
            Read((byte)L); long val = 0; if (IsBE)
                for (sbyte i = 0; i < L; i++) { val <<= 8; val |= _buf[i]; }
            else
                for (sbyte i = L; i > 0; i--) { val <<= 8; val |= _buf[i - 1]; }
            return val;
        }

        public ulong UIntFromArray(sbyte L) => UIntFromArrayEndian(L, false);
        public ulong UIntFromArrayEndian(sbyte L, bool IsBE)
        {
            Read((byte)L); ulong val = 0; if (IsBE)
                for (sbyte i = 0; i < L; i++) { val <<= 8; val |= _buf[i]; }
            else
                for (sbyte i = L; i > 0; i--) { val <<= 8; val |= _buf[i - 1]; }
            return val;
        }

        private void Read(byte Length) { if (LongPosition < LongLength) _Stream.Read(_buf, 0, Length); }

        public byte ReadByte() { return (byte)UIntFromArrayEndian(1, false); }
        public ushort ReadUInt16() { return (ushort)UIntFromArrayEndian(2, false); }
        public int ReadInt32() { return (int)IntFromArrayEndian(4, false); }
        public uint ReadUInt32() { return (uint)UIntFromArrayEndian(4, false); }
        public ulong ReadUInt64() { return UIntFromArrayEndian(8, false); }
        public int ReadInt32Endian() { return (int)IntFromArrayEndian(4, IsBE); }
        public float ReadSingle() { uint a = ReadUInt32(); return *(float*)&a; }
        public double ReadDouble() { ulong a = ReadUInt64(); return *(double*)&a; }

        public byte[] NullTerminated() => NullTerminated(0x00);
        public byte[] NullTerminated(byte End)
        {
            List<byte> s = new List<byte>();
            while (true && LongPosition > 0 && LongPosition < LongLength)
            {
                byte a = ReadByte();
                if (a == 0x00)
                    break;
                else
                    s.Add(a);
            }
            return s.ToArray();
        }
    }
}
